#include "ntp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h> 
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <endian.h>
#include "daemon.h"

/**
 * @brief 将域名解析为IP地址
 * 
 * @param[in] host 解析域名名称
 * @return struct hostent* Description of data base entry for a single host.
 */
__USED static struct hostent *host_by_ip_address(const char *host) 
{
    struct hostent* hostInfo = gethostbyname(host);
    return hostInfo;
}

/**
 * @brief Set the socket blocking object.
 * 
 * @param[in] fd        socket嵌套字
 * @param[in] blocking  true,设置为阻塞； false,设置为非阻塞
 * @return int 0,成功； -1,失败
 */
__USED static int set_socket_blocking(int fd, bool blocking) 
{
    /* 获取嵌套字模式 */
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0) {
        printf("get flags error:%s\n", strerror(errno));
        return -1;
    }

    if (blocking == false) {
        flags |= O_NONBLOCK;
    } else {
        flags &= ~O_NONBLOCK;
    }
    
    /* 设置嵌套字模式 */
    if (fcntl(fd, F_SETFL, flags) < 0) {
        printf("set flags error:%s\n", strerror(errno));
        return -1;
    }

    return 0;
}

/**
 * @brief 通过已非阻塞的方式建立socket连接
 * 
 * @return int ： file descriptor for the new socket, or -1 for errors.
 */
__USED static int socket_non_blocking(int __domain, int __type, int __protocol)
{
    int fd = socket(__domain, __type, __protocol);
    if (fd < 0) {
        printf("create socket failed,error:%s.\n", strerror(errno));
        return -1;
    }

    /* 设置套接字为非阻塞 */
    if (set_socket_blocking(fd, false) < 0) {
        close(fd);
        return -2;
    }

    return fd;
}

/**
 * @brief 使用非阻塞的方式连接网络服务器，但是改函数本身是阻塞
 * 
 * @param[in] __fd      socket嵌套字
 * @param[in] __addr    服务器IP地址
 * @param[in] __len     LEN bytes long
 * @param[in] __timeout 连接超时时间
 * @return int 0连接成功； -1连接失败； -2连接超时
 */
__USED static int connect_non_blocking(int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len, int __timeout)
{
    fd_set fdr, fdw;
    struct timeval timeout;
    int err = 0;
    socklen_t errlen = sizeof(err);

    /* 将IP地址转化为字符串 */
    char ip_str[INET_ADDRSTRLEN + 1];   //INET_ADDRSTRLEN这个宏系统默认定义 16
    memset(ip_str, 0, sizeof(ip_str));
    inet_ntop(AF_INET, &(((struct sockaddr_in *)__addr)->sin_addr), 
              ip_str, INET_ADDRSTRLEN);

    int rc = connect(__fd, __addr, __len);
    if (rc == 0) {
        return 0;
    } else if (errno == EINPROGRESS) {
        /* 正在处理连接 */
        FD_ZERO(&fdr);
        FD_ZERO(&fdw);
        FD_SET(__fd, &fdr);
        FD_SET(__fd, &fdw);

        /* 设置延迟超时时间， 默认/最大 延迟为 75s */
        if (__timeout <= 0 || __timeout >= 75000) {
            __timeout = 75000;
        }
        timeout.tv_sec = __timeout / 1000;
        timeout.tv_usec = __timeout % 1000 * 1000000;

        rc = select(__fd + 1, &fdr, &fdw, NULL, &timeout);
        /* select调用失败 */
        if (rc < 0) {
            goto connect_error;
        }
        
        /* 连接超时 */
        if (rc == 0) {
            goto connect_error;
        }

        /*[1] 当连接成功建立时，描述符变成可写,rc=1*/
        if (rc == 1 && FD_ISSET(__fd, &fdw)) {
            return 0;
        }

        /*[2] 当连接建立遇到错误时，描述符变为即可读，也可写，rc=2 遇到这种情况，可调用getsockopt函数*/
        if (rc == 2) {
            if (getsockopt(__fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
                fprintf(stderr, "%s getsockopt(SO_ERROR): %s", ip_str, strerror(errno));
                return -1;
            }

            if (err) {
                errno = err;
                goto connect_error;
            }
        }
    }

connect_error:
    fprintf(stderr, "%s connect error : %s.\n", ip_str,  strerror(errno));
    return -1;
}

const ntp_server  ntp_server_list[] = {
    /* 中国科学院国家授时中心 */
    {"ntp.ntsc.ac.cn" ,     123},

    /* China — cn.pool.ntp.org */
    {"0.cn.pool.ntp.org" ,  123},
    {"1.cn.pool.ntp.org" ,  123},
    {"2.cn.pool.ntp.org" ,  123},
    {"3.cn.pool.ntp.org" ,  123},
    
    /* 其他ntp服务器地址 */
    {"182.92.12.11",        123},

    /* 列表结束定义 */
    {NULL, 0},
};


#define VERSION_3           3
#define VERSION_4           4

#define MODE_CLIENT         3
#define MODE_SERVER         4
 
#define NTP_LI              0
#define NTP_VN              VERSION_3   
#define NTP_MODE            MODE_CLIENT
#define NTP_STRATUM         0
#define NTP_POLL            4
#define NTP_PRECISION       -6

#define NTP_HLEN            48

#define TIMEOUT             10

#define BUFSIZE             1500

#define JAN_1970            0x83aa7e80

#define NTP_CONV_FRAC32(x)  (uint64_t) ((x) * ((uint64_t)1<<32))    
#define NTP_REVE_FRAC32(x)  ((double) ((double) (x) / ((uint64_t)1<<32)))   

#define NTP_CONV_FRAC16(x)  (uint32_t) ((x) * ((uint32_t)1<<16))    
#define NTP_REVE_FRAC16(x)  ((double)((double) (x) / ((uint32_t)1<<16)))    


#define USEC2FRAC(x)        ((uint32_t) NTP_CONV_FRAC32( (x) / 1000000.0 )) 
#define FRAC2USEC(x)        ((uint32_t) NTP_REVE_FRAC32( (x) * 1000000.0 )) 


#define NTP_LFIXED2DOUBLE(x)    ((double) ( ntohl(((struct l_fixedpt *) (x))->intpart) - JAN_1970 + FRAC2USEC(ntohl(((struct l_fixedpt *) (x))->fracpart)) / 1000000.0 ))   

// using namespace std;
struct s_fixedpt {
    uint16_t    intpart;
    uint16_t    fracpart;
};

struct l_fixedpt {
    uint32_t    intpart;
    uint32_t    fracpart;
};


struct ntphdr {
#if __BYTE_ORDER == __BID_ENDIAN
    unsigned int    ntp_li:2;
    unsigned int    ntp_vn:3;
    unsigned int    ntp_mode:3;
#endif
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int    ntp_mode:3;
    unsigned int    ntp_vn:3;
    unsigned int    ntp_li:2;
#endif
    uint8_t         ntp_stratum;
    uint8_t         ntp_poll;
    int8_t          ntp_precision;
    struct s_fixedpt    ntp_rtdelay;
    struct s_fixedpt    ntp_rtdispersion;
    uint32_t            ntp_refid;
    struct l_fixedpt    ntp_refts;
    struct l_fixedpt    ntp_orits;
    struct l_fixedpt    ntp_recvts;
    struct l_fixedpt    ntp_transts;
};


in_addr_t inet_host(const char *host)
{
    in_addr_t saddr;
    struct hostent *hostent;

    if ((saddr = inet_addr(host)) == INADDR_NONE) {
        if ((hostent = gethostbyname(host)) == NULL)
            return INADDR_NONE;

        memmove(&saddr, hostent->h_addr_list[0], hostent->h_length);
    }

    return saddr;
}


int get_ntp_packet(void *buf, size_t *size)  //构建并发送NTP请求报文
{
    struct ntphdr *ntp;
    struct timeval tv;


    if (!size || *size<NTP_HLEN)
        return -1;

    memset(buf, 0, *size);

    ntp = (struct ntphdr *) buf;
    ntp->ntp_li = NTP_LI;
    ntp->ntp_vn = NTP_VN;
    ntp->ntp_mode = NTP_MODE;
    ntp->ntp_stratum = NTP_STRATUM;
    ntp->ntp_poll = NTP_POLL;
    ntp->ntp_precision = NTP_PRECISION;

    gettimeofday(&tv, NULL);  //把目前的时间用tv 结构体返回
    ntp->ntp_transts.intpart = htonl(tv.tv_sec + JAN_1970);
    ntp->ntp_transts.fracpart = htonl(USEC2FRAC(tv.tv_usec));

    *size = NTP_HLEN;

    return 0;
}



double get_rrt(const struct ntphdr *ntp, const struct timeval *recvtv)  //往返时延
{
    double t1, t2, t3, t4;

    t1 = NTP_LFIXED2DOUBLE(&ntp->ntp_orits);
    t2 = NTP_LFIXED2DOUBLE(&ntp->ntp_recvts);
    t3 = NTP_LFIXED2DOUBLE(&ntp->ntp_transts);
    t4 = recvtv->tv_sec + recvtv->tv_usec / 1000000.0;

    return (t4 - t1) - (t3 - t2);
}


double get_offset(const struct ntphdr *ntp, const struct timeval *recvtv)  //偏移量
{
    double t1, t2, t3, t4;

    t1 = NTP_LFIXED2DOUBLE(&ntp->ntp_orits);
    t2 = NTP_LFIXED2DOUBLE(&ntp->ntp_recvts);
    t3 = NTP_LFIXED2DOUBLE(&ntp->ntp_transts);
    t4 = recvtv->tv_sec + recvtv->tv_usec / 1000000.0;

    return ((t2 - t1) + (t3 - t4)) / 2;
}


__USED __NONNULL(1, 3) 
/**
 * @brief 通过NTP域名地址获取网络时间
 * 
 * @param[out] tv           时间句柄结构体
 * @param[in] sock_fd       socket嵌套字
 * @param[in] d_name        域名地址（或IP4地址）
 * @param[in] h_port        NTP服务器端口号（通常为123）
 * @param[in] timeout_ms    延迟超时时间
 * @return int 0，成功； -1，失败
 */
static int get_ntp_time_domain_name(struct timeval * const tv, const int sock_fd, 
                                    const char *d_name, int h_port, uint32_t timeout_ms)
{
    int retval = -1;
    size_t nbytes = BUFSIZE;
    char *buffer = malloc(BUFSIZE * sizeof(char));
    fd_set readfds;
    double offset = 0.0;
    struct timeval timeout, recvtv;
    char *ip_addr = NULL;

    memset(tv, 0 , sizeof(struct timeval));

    struct hostent * host_info = host_by_ip_address(d_name);
    if (host_info == NULL || buffer == NULL) {
        printf("%s resolution IP address failed\n", d_name);
        goto return_error;
    }

    struct sockaddr_in service_addr;
    memset(&service_addr, 0 , sizeof(service_addr));
    service_addr.sin_family = AF_INET;
    service_addr.sin_port = htons(h_port);

    for (unsigned int i = 0; host_info->h_addr_list[i] != NULL; i++) {
        memcpy(&service_addr.sin_addr, &(*host_info->h_addr_list[i]), 
               host_info->h_length);
        ip_addr = inet_ntoa(*((struct in_addr*)host_info->h_addr_list[i]));

        if (connect(sock_fd, (struct sockaddr *) &service_addr, 
            sizeof(struct sockaddr)) != 0) {
            printf("%s connect error : %s\n", ip_addr, strerror(errno));
            continue;
        }

        nbytes = BUFSIZE;
        get_ntp_packet(buffer, &nbytes);
        send(sock_fd, buffer, nbytes, 0);

        FD_ZERO(&readfds);
        FD_SET(sock_fd, &readfds);
        timeout.tv_sec = timeout_ms / 1000LL;
        timeout.tv_usec = (timeout_ms % 1000LL) * 1000LL;

        if (select(sock_fd + 1, &readfds, NULL, NULL, &timeout) > 0) {
            if (FD_ISSET(sock_fd, &readfds)) {
                if ((nbytes = recv(sock_fd, buffer, BUFSIZE, 0)) < 0) {
                    printf("%s receive length is not positive\n", ip_addr);
                    continue;
                }
                //计算C/S时间偏移量
                gettimeofday(&recvtv, NULL);
                offset = get_offset((struct ntphdr *) buffer, &recvtv);    
                gettimeofday(tv, NULL);
                tv->tv_sec += (int) offset;
                tv->tv_usec += offset - (int) offset;
                retval = 0;
                goto return_error;
            } else {
                printf("%s receive error : socket unreadable\n", ip_addr);
                continue;
            }
        } else {
            printf("%s socket receive timeout\n", ip_addr);
            continue;
        }
    }

    printf("%s get ntp time failed\n", d_name);

return_error:
    free(buffer);
    return retval;
}


int main(int argc, char *argv[])
{    
    char dateBuf[64] = {0};
    char cmd[128] = {0};
    struct tm* local; 
    int sockfd;
    struct timeval tv;

    // if (daemon_init("/tmp/", 0) != 0) {
    //     perror("daemon init error");
    //     exit(-1);
    // }

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket error");
        exit(-1);
    }

    for (unsigned int i = 0; ntp_server_list[i].h_name != NULL; i++) {
        printf("ntp server \"%s\" : ", ntp_server_list[i].h_name);
        int value = get_ntp_time_domain_name(&tv, sockfd, 
                    ntp_server_list[i].h_name, ntp_server_list[i].h_port, 
                    TIMEOUT * 1000);
        if (value == 0) {
            settimeofday(&tv, NULL);
            local = localtime((time_t *) &tv.tv_sec); 
            strftime(dateBuf, 64, "%Y-%m-%d %H:%M:%S", local);
            sprintf(cmd, "date -s \"%s\"", dateBuf);
            printf("%s \n", cmd);
            break;
        }
    }

    close(sockfd);

    return 0;
}
